時間過得很快,也來到本系列文章的倒數第二篇。
昨天介紹了 Next 快取機制的前兩層 - Data Cache 和 Request Memoization。兩者主要會在 Server Components 進行 data fetching 時觸發。
快速複習一下兩者的目的:
假如還不認識兩者的讀者,建議先閱讀 Day 28 的文章。
昨天似乎成功透過禁止 Data Cache 和 Request Memoization,解決文章開頭,每頁用戶代碼一樣的問題。
但假如我現在做個小改動:一樣禁用 Data Cache,可是把 <a>
改成 <Link>
,來看看改成 soft navigation 後,會不會發生什麼改變:
不要懷疑,你沒有眼花,也不是重複播放的關係,三個頁面的用戶代碼的確不一樣,但第二輪和第三輪拜訪,用戶代碼卻還是跟第一輪一樣。所以影片就一直在 213 -> 243 -> 241 循環。
明明禁用 Data Cache 了,為什麼會這樣呢?我們接著往快取層上層找,看看到底是誰在搞鬼吧!
顧名思義,Full Route Cache 就是快取整個 route segment。在 build time,Next 會先渲染靜態路由,並快取渲染結果,避免用戶每次拜訪頁面時,都要執行 server-side rendering,加速頁面載入速度。類似我們 Day 03 提到的 ISR 作法。
所以當 server 接到要渲染 route segment 的 request 時,會先檢查快取有沒有這個 route segment 渲染結果 ( HTML & RSC Payload)。
假如不知道什麼是 RSC Payload,可參考 Day 12 文章
假如有,就直接回覆快取內容;假如沒有,就會進行渲染。渲染時假如有 fetch requests,就會檢查有沒有 Reqeust Memoization,沒有才進行 data fetching。進行 data fetching 時,會檢查有沒有 Data Cache,沒有才向 data source 拿資料。
這樣就將 Full Route Cache 和昨天的內容串起來啦!提供官方文件的 infographic 給大家參考:
( 圖片來源:https://nextjs.org/docs/app/building-your-application/caching#2-nextjs-caching-on-the-server-full-route-cache )
禁用 Full Route Cache
上述有提到,Next 只會快取靜態渲染的內容,所以只要是動態渲染就不會觸發 Full Route Cache。
動態渲染的意思是,需要在 run-time 根據 request 決定渲染內容。像是使用 cookies、searchParams 等功能 ( dynamic function ),Next 就會自動轉成動態渲染,route segment 就不會在 build-time 渲染。
假如是動態渲染,server 收到渲染 request 時會略過 Full Route Cache 的檢查,直接進行渲染。
( 圖片來源:https://nextjs.org/docs/app/building-your-application/caching#2-nextjs-caching-on-the-server-full-route-cache )
要觸發動態渲染,除了使用 dynamic functions 外,還有幾個方法:
調整 route segment 設定
可以使用 revalidate = 0
或 dynamic = 'force-dynamic'
來強迫路由動態渲染:
export const dynamic = 'force-dynamic';
export const revalidate = 0;
export default async function Page() {
return (
...
);
}
使用兩者,Full Route Cache 和 Data Cache 都不會觸發。
還有其他 route segment 設定可以使用,像是 Day 19 提到的
dynamicParams
。有興趣的讀者可參考官方文件。
禁用 Data Cache
假如某個 fetch request 使用昨天提到的 cache='no-store'
禁用 Data Cache,也會連帶禁用 Full Route Cache。
但禁用某個 fetch request 的 Data Cache 並不會影響其他 fetch request 的 Data Cache。 一樣做個小實驗:
我們在 /users fetch 兩次 /api/hello ,並讓其中一個 request 帶 {cache: 'no-store'}
,接著渲染這兩個 response:
/* app/users/page.tsx */
const fetchUserId = async (url: string, option?: RequestInit) => {
const res = await fetch(url, option);
const jsonData = await res.json();
return jsonData.message;
};
export default async function Page() {
const url = 'http://localhost:3000/api/hello';
const no_cache_option = { cache: 'no-store' };
const userId_cache = await fetchUserId(url);
const userId_no_cache = await fetchUserId(
url,
no_cache_option as RequestInit
);
return (
<div className='...'>
<h1>{userId_cache}</h1>
<h1>{userId_no_cache}</h1>
</div>
);
}
完成後,我們重新整理幾次頁面,看看會發生什麼事:
會發現,只有第二個用戶代碼隨著重新整理變化。所以雖然第二個 request 禁用 Data Cache,Full Route Cache 也隨之禁用,但並不影響第一個 request 的 Data Cache。
重置 Full Route Cache
當 Data Cache 重置時,也會清除 Full Route Cach;除此之外,重新部署也會清除 Full Route Cache。
Data Cache 除非有設定重置條件或主動重置,不然會永久存在。就算重新部署也不會清除。
回到開頭的問題,很顯然問題也不在 Full Route Cache,因為禁用 Data Cache 就不會觸發 Full Route Cache。
所以只剩最後一個選項 - Router Cache。在開始進一步調查之前,先來快速認識 Router Cache:
Router Cache 是 Next 在瀏覽器的 in-memory cache,專門負責存 route segment 的 RSC Payload。
當用戶在做 soft navigation 時,Next 渲染完目前 route segment 的內容,會快取 RSC Payload,以加快重複拜訪時,頁面載入的速度。
也適用 Prefetched 的路由
Day 20 有提到,<Link>
預設會提前載入目的地的資源。假如沒有禁用 prefetch,prefetched 路由的 RSC Payload 也會快取。
( 圖片來源:https://nextjs.org/docs/app/building-your-application/caching#router-cache )
失效條件
revalidatePath
或 revalidateTag
( 可參考昨天文章 )router.refresh
( 可參考昨天文章 )補充一下,雖然官方文件說 Route Handler 可以使用官方 API 的
cookies.set
function,但根據 issue#52799 官方的說法,cookies.set
無法在 rendering 時使用。意思是,假如要在 Route Handler 使用
cookies.set
,要直接在瀏覽器拜訪 API 連結,或用 postman 打這支 API 才有效,小弟實測也確實如此,還蠻玄的...所以假如要使用
cookies.set
,官方建議在 middleware 或 Server Actions 中使用。
快速認識 Router Cache 後,我們打開 DevTools 的 Network,觀察一下路由切換時的是否有向 server 取 RSC Payload:
可以觀察到,當我們進到第一次 /welcome、/dashbaord、/shop 時,會向 server 取對應的 RSC Payload,但後續再次拜訪,就不會再取一次。
假如仔細觀察上方的影片,會發現,用戶代碼也是第二輪開始停止更新,與停止載入新的 RSC Payload 的時機一樣。
假如回顧昨天的文章,的確使用 <a>
製造 hard navigation,或使用 revalidatePath
與 router.refresh
都可以讓用戶代碼更新。看樣子開頭的問題,確實是 Router Cache 造成的。
那我們有辦法直接禁用它嗎?
禁用 Router Cache
我們來看官方怎麼說:
It's not possible to opt out of the Router Cache.
很遺憾地,我們無法禁用 Router Cache。所以假如希望頁面切換時,能自動清除 Router Cache,目前只能捨棄 <Link>
改用原生的 <a>
。
認識完四個快取機制後,最後我們再回顧一次昨天前言的圖:
( 圖片來源:https://nextjs.org/docs/app/building-your-application/caching )
簡單來說,當使用者進到頁面,假如有 Server Components:
以上,就是 Next.js 13 快取機制的介紹,Next.js 13 的介紹也到這邊告一段落。希望有幫助到大家,在開發 Next 專案時,工具技術的選擇上,能多一些參考依據,以及 debug 時,能多一些線索。
大致認識完 Next.js 13 的基本功能後,下一步呢?這部分就留到最後一天和大家分享囉。
謝謝大家耐心的閱讀,我們明天見!